﻿using System;
using System.Collections.Generic;

namespace Com.PhilChuang.Utils
{
	public class ServiceLocatorBase : IServiceLocator
	{
		/// <summary>
		/// Maps interface type to ServiceLocatorHelper
		/// </summary>
		private Dictionary<Type, ServiceLocatorHelper> m_Helpers;

		/// <summary>
		/// Maps type interface to instantiated instance
		/// </summary>
		private Dictionary<Type, Object> m_ImplCache;
		private readonly Object lock_m_ImplCache = new object ();
		private Dictionary<Type, List<Action<Object>>> m_ImplCachedActions;

		protected virtual Func<Type, Object> DefaultInitializer
		{ get { return (t) => Activator.CreateInstance (t); } }

		public ServiceLocatorBase ()
		{
			m_Helpers = new Dictionary<Type, ServiceLocatorHelper> ();
			m_ImplCache = new Dictionary<Type, object> ();
			m_ImplCachedActions = new Dictionary<Type, List<Action<object>>> ();
		}

		public void AddMapping (Type implType)
		{
			AddMapping (implType, implType, true);
		}

		public void AddMapping (Type implType, bool cacheImpl)
		{
			AddMapping (implType, implType, cacheImpl);
		}

		public void AddMapping (Type implType, bool cacheImpl, Func<Type, Object> init)
		{
			AddMapping (implType, implType, cacheImpl, init);
		}

		public void AddMapping (Type intfcType, Type implType)
		{
			AddMapping (intfcType, implType, true);
		}

		public void AddMapping (Type intfcType, Type implType, bool cacheImpl)
		{
			AddMapping (intfcType, implType, cacheImpl, DefaultInitializer);
		}

		public void AddMapping (Type intfcType, Type implType, bool cacheImpl, Func<Type, Object> init)
		{
			m_Helpers[intfcType] = new ServiceLocatorHelper () {
			                                        	InterfaceType = intfcType,
			                                        	ImplementationType = implType,
			                                        	Cache = cacheImpl,
			                                        	Initializer = init
			                                        };
		}

		public T GetImpl<T> ()
		{
			var intfcType = typeof (T);
			lock (lock_m_ImplCache)
			{
				if (m_ImplCache.ContainsKey (intfcType))
					return (T) m_ImplCache[intfcType];
			}

			if (!m_Helpers.ContainsKey (intfcType))
				throw new ArgumentException ("Can't get impl for {0}".FormatWith (intfcType.Name));

			var helper = m_Helpers[intfcType];

			var impl = (T) helper.Initializer.Invoke (helper.ImplementationType);
			// TODO check to see if impl has Disposed event handler, tie into it to remove from cache

			if (helper.Cache)
			{
				CacheImpl (impl);
			}

			return impl;
		}

		public void CacheImpl<T> (T impl)
		{
			var type = typeof (T);
			lock (lock_m_ImplCache)
			{
				m_ImplCache[type] = impl;

				if (!m_ImplCachedActions.ContainsKey (type)) return;
				m_ImplCachedActions[type].ForEach (a => a (impl));
				m_ImplCachedActions[type].Clear ();
				m_ImplCachedActions.Remove (type);
			}
		}

		public void WhenImplCached<T> (Action<T> a)
		{
			var type = typeof (T);
			lock (lock_m_ImplCache)
			{
				object implObj;
				if (m_ImplCache.TryGetValue (type, out implObj))
				{
					a ((T) implObj);
					return;
				}

				if (!m_ImplCachedActions.ContainsKey (type))
					m_ImplCachedActions[type] = new List<Action<object>> ();

				m_ImplCachedActions[type].Add (obj => a ((T) obj));
			}
		}

		private struct ServiceLocatorHelper
		{
			public Type InterfaceType { get; set; }
			public Type ImplementationType { get; set; }
			public bool Cache { get; set; }
			public Func<Type, Object> Initializer { get; set; }
		}
	}
}
